Skip to content

Fix asymmetric Observation scope propagation in Kotlin Coroutines#36955

Open
arnabnandy7 wants to merge 1 commit into
spring-projects:mainfrom
arnabnandy7:fix/issue-36929-propagation-scope
Open

Fix asymmetric Observation scope propagation in Kotlin Coroutines#36955
arnabnandy7 wants to merge 1 commit into
spring-projects:mainfrom
arnabnandy7:fix/issue-36929-propagation-scope

Conversation

@arnabnandy7

Copy link
Copy Markdown

Summary

This PR addresses issue #36929 where Kotlin flow-to-flux subscriptions using PropagationContextElement open Observation scopes asymmetrically, causing AssertionError crashes in ObservationThreadLocalAccessor.restore.

Cause of the Issue

During undispatched coroutine context modifications (e.g., from ChannelFlowKt.withContextUndispatched nested inside the flow's execution path), updateThreadContext is called nestedly on the same thread. When a coroutine suspends within the nested block:

  1. The execution pauses, meaning the finally block of the nested block (which calls restoreThreadContext to close the nested scope) is NOT executed.
  2. The outer coroutine dispatcher's suspension cleanup runs and executes the outer restoreThreadContext.
  3. Because the nested scope was never closed, the outer restoreThreadContext attempts to close the outer scope but finds a dirty thread-local observation stack (with the nested scope still on top), causing Micrometer's assertion to trip.

Solution

We introduce a static ThreadLocal<Integer> nestingDepth tracker inside PropagationContextElement:

  • When updateThreadContext is called, we increment nestingDepth.
  • If the depth is greater than 1 (meaning propagation is already active on the current thread), we return a no-op Scope implementation, avoiding nested/asymmetric thread-local scope pushes.
  • For the outermost update (depth = 1), we return a WrapperScope that delegates to the original snapshot scope and resets the nestingDepth to 0 when closed.

Verification

Added a unit test nestedInvocations in PropagationContextElementTests which simulates nested context updates and outer/nested restorations, verifying that no assertion errors are thrown and the Observation stack remains balanced. All tests in spring-core compiled and passed.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 20, 2026
Introduce a ThreadLocal nesting check inside PropagationContextElement to prevent duplicate/asymmetric thread-local scope updates during nested undispatched coroutine context changes.

Closes spring-projects#36929

Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>
@arnabnandy7 arnabnandy7 force-pushed the fix/issue-36929-propagation-scope branch from 384f831 to 40ce895 Compare June 20, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants